/**
 * @file Tilemap.hpp
 *
 * @date 01/02/2012
 * @author Tales 
 *
 */

#ifndef TILEMAP_HPP_
#define TILEMAP_HPP_

#include "common.hpp"
#include "Layer.hpp"
#include "Rect.hpp"

namespace wolf {

/**
 * @brief A tiled map layer.
 * @ingroup stdMod
 *
 *
 * A tile map is useful way to describe scenario. We have a set of tiles (we call it... Tileset!) that are combined to produce
 *  in an larger figure. The tileset is a surface divided in rows and columns (referred as tilesetRows and tilesetColumns) like
 *  a table or a matrix. Each cell is tile have the same size than the orders and is receive a number, starting at one end
 *  ending in at tilesetRows*tilesetColumns, numbered in order left to right and top to bottom.
 *
 * We have another matrix, with an arbitrary size, called map. Each cell has a number that corresponds to a tile number. So if a
 *  map cell has the number one, it represents the tile on the first column and the first row. If the number is two, it represents
 *  the tile on the second column and first row, and so on. You can change any time the tile represented by a cell. When it is draw,
 *  it draw the map from left to right and top to bottom, putting the correct tile for each cell. @see TilesetTest.
 *
 * If the tile number is zero, it represents the no tile and negative represents an animated tile. We define the slot's quantity
 *  for animated tiles on the constructor. We can't alter that number, but we can change the animation behavior.
 *
 * It has several functions and settings, so we divided in 4 groups:
 * -# Tileset settings;
 * -# Animation settings (in fact it is also a tileset setting, but is so specific that deserves its own group);
 * -# map settings;
 * -# view settings.
 *
 * @section tilesetsettings Tileset Settings
 *
 * Configure how the tileset work. A tileset is defined by a surface (must be not NULL) and the number of rows and columns.
 *  All the three attributes are defined on constructor. The surface is divided in blocks (or cells) called tiles. Each tile
 *  has the same width and same height. The tile width is the surface width divided by the number of columns and the tile
 *  height is equal to the surface height divided by the number of rows.
 *
 * To be enable to check whether the tiles are valid we must to know how much they are. As the checking process is
 *  expensive, we do it rarely, but to this being possible we need to have a constant number of tiles. Because of this,
 *  the number of columns and the rows are constant so the number of tiles are constant too (tilesNumber =
 *  tilesetColumns * tilesetRows).
 *
 * The surface itself can be changed. As the tile width and height are proportional to the surface width an height we have
 *  to re-evaluate them every time the surface is changed, but these costs are not expensive. But if the tile width and/or
 *  height are changed, the rendered tilemap will have a different size too and it have some impact on the <b>View Settings</b>.
 *  To avoid bugs, is @b recommended (<i>but not obligatory</i>) to replace a surface only for another surface having the
 *  same width and height.
 *
 * @todo implement checking.
 *
 * @section animationsettings Animation Settings
 *
 * Animation is represented by negative indices on the tile map. Every index corresponds to a slot. The number of slots is
 *  given defined on constructor (and can't be changed by the same reasons we can't change the number of rows and columns of
 *  a tileset), which by default is 8. Each slot have a sequence of frames. All slots have the same quantity of frames (by
 *  default is 2), that represents regular tiles (with positive or zero ids).
 *
 * When the map is rendered, the negative tiles are switched to the regular one correspondent to current frame. The current
 *  frame is the same for all slots and it is changed proportionally to the engine frames and the animationSpeed. The
 *  animationSpeed, by default is 1 (that changes the current frame every time the engine frame occur). That velocity
 *  can be changed any time.
 *
 * As we say, the number of slots and animation frames are constant. But, the slot animation sequences itself can be changed
 *  any time by the setAnimation() methods. It has 3 flavors. One to change the entire animation matrix (all the slots at
 *  once), One to change all animation frames from a single slot and one to change a specific frame from a single slot.
 *
 * @section mapsettings Map Settings
 * Having our regular tiles and animation tile set, we have a tool set to make our scenarios. The big advantage appears here:
 *  If we have lots of a grass on screen, why we don't just make a small grass pattern and repeat it everywhere it is needed?
 *  Well, the mapping do it! We have the tilemap matrix (usually bigger than tileset matrix) where on each cell (also
 *  called tile) we put a number representing a tileset cell.
 *
 *
 *
 * For example, if we have a surface 64x32 with the first half filled with grass pattern and the second with sand pattern,
 *  We would have a 3 valid regular tiles:
 *   - 0) an empty or transparent tile;
 *   - 1) grass;
 *   - 2) sand;
 *
 * If we want to fill an entire screen with the size of 640x480 with grass, the code snipet below would be enough to
 * configure the Tilemap:
 * @code
 *  int mapWidth = 640/32; // The map width in columns
 *  int mapHeight = 480/32; // The map heigh in rows
 *	Tilemap myTilemap(mySurface, 2, 1, mapWidth, mapHeight);
 *	for( int row = 0; row < mapHeight; ++row ){
 *	  for( int column = 0; column < mapWidth; ++ column){
 *	    myTilemap.tileAt(column, row) = 1; // Grass is one, remember?
 *	  }
 *	}
 * @endcode
 *
 * @section viewsettings View Settings
 * @todo document it
 *
 */
class Tilemap: public Layer {
public:
	/**
	 * @brief Constructor.
	 *
	 * @param surface_ A surface.
	 * @param tilesetColumns_ the tileset collumns number
	 * @param tilesetRows_ the tileset rows numbre
	 * @param mapWidth_ the map width in columns.
	 * @param mapHeight_ the map height in rows.
	 * @param animationSlots_ the number of slots. Zero turns off the animation. It should be less than 2^15;
	 * @param animationFrames_ the number of frames the animated tiles will have. It must be larger than 0.
	 * @param own if true, the surface will be freed on destruction of this object.
	 * @param depth the depth.
	 */
	Tilemap(SDL_Surface *surface_, Uint8 tilesetColumns_, Uint8 tilesetRows_,
			Uint16 mapWidth_, Uint16 mapHeight_, Uint16 animationSlots_ = 8,
			Uint8 animationFrames_ = 2, bool own = false, int depth = 0);
	/**
	 * @brief Constructor.
	 *
	 * @param surface_ A surface.
	 * @param tilesetColumns_ the tileset collumns number
	 * @param tilesetRows_ the tileset rows numbre
	 * @param mapWidth_ the map width in columns.
	 * @param mapHeight_ the map height in rows.
	 * @param tiles_ the tiles. The size must to be at least equal to _mapWidth_ * mapHeight_. The content is copied to an
	 *  internal structure and therefore it do @b not takes the ownership for it.
	 * @param animationSlots_ the number of slots. Zero turns off the animation
	 * @param animationFrames_ the number of frames the animated tiles will have.
	 * @param own if true, the surface will be freed on destruction of this object.
	 * @param depth the depth.
	 */
	Tilemap(SDL_Surface *surface_, Uint8 tilesetColumns_, Uint8 tilesetRows_,
			Uint16 mapWidth_, Uint16 mapHeight_, const Sint16 *tiles_,
			Uint16 animationSlots = 8, Uint8 animationFrames = 2, bool own =
					false, int depth = 0);
	/**
	 * @brief Destructor.
	 */
	~Tilemap();

	//////////////////////////
	//// Tileset Settings ////
	//////////////////////////

	/**
	 * @brief Returns the current tileset.
	 * @return a pointer to the surface containing the tileset.
	 */
	SDL_Surface *getSurface() const;
	/**
	 * @brief Changes the tileset surface.
	 * @param surface the new tileset surface.
	 * @param own true if it you want to pass the surface's ownership to this.
	 */
	void setSurface(SDL_Surface *surface, bool own = false);
	/**
	 * @brief gets the number of columns on the tileset.
	 */
	Uint8 getTilesetColumns() const;
	/**
	 * @brief gets the number of rows on the tileset.
	 */
	Uint8 getTilesetRows() const;
	/**
	 * @brief Returns the total number of tiles in a tileset.
	 * @return
	 */
	Uint32 getTilesetSize() const {
		return getTilesetColumns() * getTilesetRows();
	}

	/**
	 * @brief Gets the tile's height.
	 * @return the number of pixels each tile has vertically.
	 */
	Uint8 getTileHeight() const;
	/**
	 * @brief Gets the tile's height.
	 * @return the number of pixels each tile has horizontally.
	 */
	Uint8 getTileWidth() const;

	//////////////////////////
	////Animation Settings////
	//////////////////////////

	/**
	 * @brief Gets the animation matrix
	 * @return a linearized matrix of animated tiles.
	 * @see setAnimation()
	 */
	const Uint16 *getAnimation() const;
	/**
	 * @brief Gets the animation sequence for a given slot
	 * @param slot A negative number (-1 to -getAnimationSlots(), inclusive) representing the tile index.
	 * @return The vector containing the animation sequence for the given slot.
	 */
	const Uint16 *getAnimation(Sint16 slot) const;
	/**
	 * @brief Gets the animation frame from a given slot
	 * @param slot A negative number (-1 to -getAnimationSlots(), inclusive) representing the tile index.
	 * @param frame frame the frame to change. It should be equal or larger than zero and lesser than getAnimationFrames().
	 * @return The tile index for the given slot and frame.
	 */
	Uint16 getAnimation(Sint16 slot, Uint8 frame) const;
	/**
	 * @return The number of frames for each slot.
	 */
	Uint8 getAnimationFrames() const;
	/**
	 * @return The number of slots.
	 */
	Uint16 getAnimationSlots() const;
	/**
	 * @return the animation speed
	 */
	float getAnimationSpeed() const;
	/**
	 * @brief Changes the animation matrix
	 * @param animation the animation matrix. It's size should be equal to animationSlots * animationFrames.
	 *  Larger matrices will have extended values ignored. Lesser matrices have undefined behavior, but probably will crash.
	 *  All the frames of each slot are put consecutively and after the last frame of a slot is followed by the first
	 *  of the next. The order is first the slot -1, after the slot -2 and so on. The values are copied, so don't worry
	 *  about freeing the matrix after this calling. The values should be between 0 and getTilesetSize()-1, inclusive.
	 */
	void setAnimation(const Uint16 *animation);
	/**
	 * @brief Changes an animation sequence for a given slot.
	 * @param slot A negative number (-1 to -getAnimationSlots(), inclusive) representing the tile index.
	 * @param frames A vector with the animation sequence. It should have the size equal to getAnimationFrames().
	 *  Larger vector will have extended values ignored. Lesser vectors have undefined behavior, but probably will crash.
	 *  The values should be between 0 and getTilesetSize()-1, inclusive.
	 */
	void setAnimation(Sint16 slot, const Uint16 *frames);
	/**
	 * @brief Changes an animation frame for a given slot.
	 * @param slot A negative number (-1 to -getAnimationSlots(), inclusive) representing the tile index.
	 * @param frame the frame to change. It should be equal or larger than zero and lesser than getAnimationFrames().
	 * @param value The new value. The value should be between 0 and getTilesetSize()-1, inclusive.
	 */
	void setAnimation(Sint16 slot, Uint8 frame, Uint16 value);
	/**
	 * @param animationVelocity the new velocity
	 */
	void setAnimationSpeed(float animationVelocity);

	//////////////////////////////////////
	////Map Settings and Manipulations////
	//////////////////////////////////////

	/**
	 * @brief Gets the tile at given position.
	 * @param x X-axis (the column number, from left to right starting at zero to getMapWidth()-1)
	 * @param y Y-axis (the row number, from top to bottom starting at zero to getMapHeight()-1)
	 * @return The tile Number.
	 */
	Sint16 tileAt(size_t x, size_t y) const;
	/**
	 * @brief Access the tile at given position.
	 * @param x X-axis (the column number, from left to right starting at zero to getMapWidth()-1)
	 * @param y Y-axis (the row number, from top to bottom starting at zero to getMapHeight()-1)
	 * @return The tile Number.
	 */
	Sint16 & tileAt(size_t x, size_t y);
	/**
	 * @return The linearized matrix of tiles
	 */
	const Sint16 *getTiles() const;
	/**
	 * @brief Changes the tiles matrix. It's size should be equal to mapWidth * mapHeight.
	 * Larger matrices will have extents ignored. Lesser matrices have undefined behavior, but probably will crash.
	 * @param tiles the new tiles.
	 */
	void setTiles(const Sint16 *tiles);
	/**
	 * @brief Returns the map height.
	 * @return the number of tiles it has vertically.
	 */
	Uint16 getMapHeight() const;
	/**
	 * @brief Returns the map width.
	 * @return the number of tiles it has horizontally.
	 */
	Uint16 getMapWidth() const;

	///////////////////////////////////////
	////View Settings and Manipulations////
	///////////////////////////////////////
	/**
	 * @brief The horizontal loop flag.
	 * @return whether it has horizontal loop enabled.
	 */
	bool isHorizontalLoop() const;

	/**
	 * @brief The vertical loop flag.
	 * @return whether it has vertical loop enabled.
	 */
	bool isVerticalLoop() const;

	/**
	 * @brief Changes the horizontal loop flag.
	 * @param horizontalLoop True to turn on, false to turn off.
	 */
	void setHorizontalLoop(bool horizontalLoop);

	/**
	 * @brief Changes the vertical loop flag.
	 * @param verticalLoop True to turn on, false to turn off.
	 */
	void setVerticalLoop(bool verticalLoop);

	/**
	 * @brief The first pixel of the mapping to be draw on screen.
	 * @return the x-position of that pixel.
	 */
	Sint16 getOffsetX() const;

	/**
	 * @brief The first pixel of the mapping to be draw on screen.
	 * @return the y-position of that pixel.
	 */
	Sint16 getOffsetY() const;

	/**
	 * @brief the max quantity of the pixel to show on screen.
	 *
	 * If the verticalLoop flag is on, the number of pixels really shown will be exactly the same.
	 *  If not, it can be less than it.
	 * @return the number of pixel to show vertically.
	 */
	Uint16 getViewHeight() const;

	/**
	 * @brief the max quantity of the pixel to show on screen.
	 *
	 * If the horizontalLoop flag is on, the number of pixels really shown will be exactly the same.
	 *  If not, it can be less than it.
	 * @return the number of pixel to show horizontally.
	 */
	Uint16 getViewWidth() const;

	/**
	 * @brief The @b screen position where the first pixel will be put.
	 * @return the x-axis position.
	 */
	int getX() const;

	/**
	 * @brief The @b screen position where the first pixel will be put.
	 * @return the x-axis position.
	 */
	int getY() const;

	/**
	 * @brief Changes the first pixel to show on screen.
	 * @param offsetX the horizontal position.
	 */
	void setOffsetX(Sint16 offsetX);

	/**
	 * @brief Changes the first pixel to show on screen;
	 * @param offsetY the vertical position
	 */
	void setOffsetY(Sint16 offsetY);

	/**
	 * @brief the max quantity of the pixel to show on screen.
	 * @param viewHeight the new vertical value.
	 */
	void setViewHeight(Uint16 viewHeight);

	/**
	 * @brief the max quantity of the pixel to show on screen.
	 * @param viewWidth the new horizontal value.
	 */
	void setViewWidth(Uint16 viewWidth);

	/**
	 * @brief The @b screen position where the first pixel will be put.
	 * @param x the new horizontal position.
	 */
	void setX(int x);

	/**
	 * @brief The @b screen position where the first pixel will be put.
	 * @param y the new vertical position.
	 */
	void setY(int y);

	/**
	 * @brief Draws the tilemap on the destination.
	 * @param dst the destination surface.
	 */
	void draw(SDL_Surface *dst);
private:
	void _init(SDL_Surface *surface_, Uint8 tilesetColumns_, Uint8 tilesetRows_,
			Uint16 mapWidth_, Uint16 mapHeight_, Uint16 animationSlots_,
			Uint8 animationFrames_);
    void drawTile(Sint16 tile, Sint16 column, int iniX, Sint16 row, int iniY, SDL_Surface *& dst);
    //Tileset settings
    SDL_Surface *surface;
    Uint8 tileWidth;
    Uint8 tileHeight;
    Uint8 tilesetColumns;
    Uint8 tilesetRows;
    //Animation settings
    float animationSpeed;
    Uint16 animationSlots;
    Uint8 animationFrames;
    Uint16 *animation;
    //Map Settings
    Uint16 mapWidth;
    Uint16 mapHeight;
    Sint16 *tiles;
    // View Settings
    bool horizontalLoop;
    bool verticalLoop;
    int x; // Position on screen
    int y; // Position on screen
    Sint16 offsetX; // First pixel to appear on screen
    Sint16 offsetY; // First pixel to appear on screen
    Uint16 viewWidth; // How many pixels to appear on screen
    Uint16 viewHeight;
};

} /* namespace wolf */
#endif /* TILEMAP_HPP_ */
